using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using WebLog.Db.SqlLogHelpers;
using Microsoft.Extensions.Configuration;

namespace WebLog
{
    public class ResourceFilterAttribute : Attribute, IAsyncResourceFilter, IResultFilter
    {
        public static IConfiguration Configuration { get; private set; }
        public ResourceFilterAttribute(IConfiguration Configuration)
        {
            ResourceFilterAttribute.Configuration = Configuration;
            EverIsWriteLog = (Configuration["EverIsWriteLog"] ?? "").ToLower().Equals("true");
            WebLogHelper.SetDbPath(Configuration["WebLogDbPath"]);
        }

        /// <summary>
        /// json序列化以小写开头
        /// </summary>
        protected JsonSerializerSettings CamelCase => new JsonSerializerSettings()
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            DateTimeZoneHandling = DateTimeZoneHandling.Local,
            DateFormatString = "yyyy-MM-dd HH:mm:ss",
        };

        public void OnResultExecuting(ResultExecutingContext resultContext)
        {
            if (resultContext == null || resultContext.Result == null || (resultContext.Result is Microsoft.AspNetCore.Mvc.BadRequestObjectResult))//预计是实体验证不通过 需要调试测试
            {
                //参数列表
                var pms = (resultContext.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)
                    ?.MethodInfo?.GetParameters()?.ToArray();
                if (pms == null || pms.Length == 0) return;
                var otherProperties = new List<PropertyInfo>();
                var types = pms.Select(e => e.ParameterType).ToList();
                types.ForEach(el => otherProperties.AddRange(el.GetProperties()));//递归增加子集（对象的属性 集合的类型）等，匹配验证，小驼峰命名法
                Action<string, string, List<PropertyInfo>> getKeyPros = null;
                Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();//变量名 中文注释的映射
                Func<Type, PropertyInfo, List<object>> GetCustomAttributes = (type, _propertyInfo) =>
                {
                    return _propertyInfo.GetCustomAttributes(type, true).ToList();
                };
                getKeyPros = (key, val, pros) =>
                {
                    if (key.Count(c => c.Equals('.')) > 5) return;
                    //只允许5层 因为有自己引用自己的情况
                    pros.ForEach(p =>
                    {
                        var labName = "";
                        var objs = GetCustomAttributes(typeof(DescriptionAttribute), p);
                        if (objs.Count > 0)
                            labName = (objs.Last() as DescriptionAttribute).Description;
                        else
                        {
                            objs = GetCustomAttributes(typeof(DisplayAttribute), p);
                            if (objs.Count > 0)
                                labName = (objs.Last() as DisplayAttribute).Name;
                        }
                        if (labName.IsNullEmpty()) labName = p.Name;
                        var keyName = p.Name;
                        if (!key.IsNullEmpty()) keyName = key + p.Name;
                        keyName = keyName.ToLower();//JsonConvert.SerializeObject(keyName, CamelCase);
                        if (!keyValuePairs.ContainsKey(keyName)) keyValuePairs.Add(keyName, val + labName);
                        else keyValuePairs[keyName] = val + labName;
                        var PropertyType = p.PropertyType;
                        PropertyType = Nullable.GetUnderlyingType(PropertyType) ?? PropertyType;
                        if (PropertyType.IsValueType || PropertyType == typeof(string)) return;//不管了直接就
                        //得到内部类型
                        var isGeneric = PropertyType.Name == "List`1" || PropertyType.BaseType == typeof(Array);
                        if (isGeneric)
                        {
                            if (PropertyType.Name == "List`1")
                            {
                                PropertyType = PropertyType.GetGenericArguments()[0];
                            }
                            else
                            {
                                string typeName = PropertyType.FullName.Replace("[]", string.Empty);
                                PropertyType = PropertyType.Assembly.GetType(typeName);
                            }

                            PropertyType = Nullable.GetUnderlyingType(PropertyType) ?? PropertyType;
                            if (PropertyType.IsValueType || PropertyType == typeof(string)) return;//不管了直接就
                            key = key + p.Name + "[x].";//前缀加自己加中括号 再加点
                            val = val + labName + "[x].";
                        }
                        else
                        {
                            key = key + p.Name + ".";//前缀加自己加点
                            val = val + labName + ".";
                        }
                        getKeyPros(key, val, PropertyType.GetProperties().ToList());//类型的底层
                    });
                };
                getKeyPros("", "", otherProperties);

                if ((resultContext.Result is Microsoft.AspNetCore.Mvc.BadRequestObjectResult))
                {
                    var bad = (resultContext.Result as Microsoft.AspNetCore.Mvc.BadRequestObjectResult);
                    var newVal = new Dictionary<string, object>();
                    if (bad.Value is Dictionary<string, object>)
                    {
                        foreach (var item in bad.Value as Dictionary<string, object>)
                        {
                            var newKey = item.Key.ToLower();
                            var reg = new Regex("\\[\\d\\]");
                            if (reg.Match(newKey).Groups.Count > 0)
                            {
                                newKey = reg.Replace(newKey, "[x]");
                            }
                            if (keyValuePairs.ContainsKey(newKey))
                            {
                                newKey = keyValuePairs[newKey].Replace("[x]", "");
                                newVal.Add(newKey + "(" + item.Key + ")", item.Value);
                            }
                            else newVal.Add(item.Key, item.Value);
                        }
                    }
                    var badVal = JsonConvert.SerializeObject(newVal);
                    bad.StatusCode = 200;
                    dynamic ex = Comm.TypeFactory.CreateBizException(badVal);
                    var re = Comm.TypeFactory.CreateBaseBizResult("验证不通过：" + ex.Message, errorCode: ex.ErrorCode, errorData: ex.ErrorData);
                    bad.Value = re;
                    re.Data = badVal;
                    if (pms.Length == 1)//只有一个的时候
                    {
                    }

                }
            }
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }

        #region 拦截器处理短时间的同样请求
        class HanderWebTask
        {
            internal DateTime? endDate;
            /// <summary>
            /// 处理时间
            /// </summary>
            internal DateTime date { get; set; }
            /// <summary>
            /// 所包含任务
            /// </summary>
            internal Task<ResourceExecutedContext> task { get; set; }
        }
        static Dictionary<string, List<HanderWebTask>> url_hands = new Dictionary<string, List<HanderWebTask>>();
        static object HandelNextLockObj = new object();
        /// <summary>
        /// 避免请求重复导致并发 （一次只执行一个相同url请求）
        /// </summary>
        /// <param name="next">后台请求体</param>
        /// <param name="url">请求路径</param>
        /// <param name="date">请求发生时间</param>
        /// <returns></returns>
        private async Task<ResourceExecutedContext> HandelNext(ResourceExecutionDelegate next
            , string url, DateTime date, bool isWirteLog = false, Action<List<string>> call = null)
        {
            if (string.IsNullOrEmpty(url) || next == null) return null;
            List<HanderWebTask> handList;
            HanderWebTask curHand = new HanderWebTask { date = date };
            lock (HandelNextLockObj)//取到上一个 把自己加入队列
            {
                if (!url_hands.ContainsKey(url))
                {
                    url_hands.Add(url, new List<HanderWebTask>());
                }
                handList = url_hands[url];
                handList.Add(curHand);//加入当前线程
            }
            ResourceExecutedContext result = null;
            bool isNeedAwait;
            do
            {
                lock (HandelNextLockObj)
                    isNeedAwait = handList.Count > 1;//因为自身就是一个元素
                if (isNeedAwait)
                {
                    var interval = 200;//间隔0.2秒 
                    /*// 可以改成2200 然后启动项目 (最好是启动项目 然后来执行url 不然可能会因为请求太快 看不到延时)
                    * 再打开两个相同url的链接（get请求） 就能看到阻塞了 
                    * 可以使用 链接： http://localhost:9076/api/ProductionPlan/GetProductionPlanList?page=1&rows=20&currentPageIndex=0&pageSize=20
                    * 原理是 刚打开项目 后台很多注入未发生 当新的请求执行，后台才开始进行注入， 故而第二个请求进来，前面的请求还在后台执行
                    */
                    //循环 只要有请求存在 并且请求不是当前这个请求 就休眠而后等待
                    bool isSleep = false;//使用了挂起线程的时候报错： PlatformNotSupportedException: Thread suspend is not supported on this platform. 
                    bool isUseLastData = false;//是否使用之前的请求的结果 一般情况下只有get这么做合适 post的话会导致不对应
                    //所以 isSleep false无法正确运行
                    /*
                    把休眠写在更新 是否等待 标识下面 比较容易测试 看到请求先后完成；
                    否则可能休眠完了发现不需要休眠 然后就执行代码 然后两个请求响应时间接近 看不出区别
                    */
                    if (isSleep)//每次遇到有未处理完的 就睡一会儿
                        Thread.Sleep(interval);
                    else//每次遇到有未处理完的 就通过join 排列在其后面
                    {
                        HanderWebTask hand;
                        lock (HandelNextLockObj)//拿到第一个hand
                            hand = handList.OrderBy(h => h.date).First();
                        if (hand == curHand) isNeedAwait = false;
                        else
                        {
                            if (hand.task == null)//任务为空就返回去休眠一会儿
                                continue;
                            try
                            {
                                hand.task.Wait();//等待这个任务运行完 再往下走
                                if (isUseLastData)
                                {
                                    result = hand.task.Result;//直接使用之前的任务的返回值
                                    break;
                                }
                            }
                            catch (System.Exception ex)
                            {
                                Thread.Sleep(interval);
                            }
                        }
                    }
                }
            } while (isNeedAwait);
            if (result == null)
            {
                curHand.task = Task.Run(() =>
                {
                    if (isWirteLog && call != null)//记录日志 并且有回调函数
                    {
                        //申请监听id
                        Guid req_id = Guid.Empty;
                        var begin = new Task(() => { });
                        var _result = begin.ContinueWith((lastTask) =>
                        {
                            req_id = LoggerHelper.SetRecordAsyncSql(url);
                            return next();
                        }, TaskContinuationOptions.ExecuteSynchronously);
                        _result.ContinueWith((lastTask) =>
                            {
                                while (!lastTask.Result.IsCompleted)
                                    lastTask.Result.Wait();//任务还没执行完
                                call(LoggerHelper.DequeueSqls(req_id));
                            }, TaskContinuationOptions.ExecuteSynchronously);
                        begin.Start();

                        return _result.Result;
                    }
                    return next();
                });
                result = await curHand.task;
            }
            curHand.endDate = DateTime.Now;
            lock (HandelNextLockObj)//执行完后清理当前 url相关对象
            {
                handList.Remove(curHand);//暂时不remove
                if (handList.Count == 0)//如果清零了 移除这个键
                    url_hands.Remove(url);
            }
            return result;
        }
        #endregion 拦截器处理短时间的同样请求
        /// <summary>
        /// 是否一直写日志（会带来大量的存储）
        /// </summary>
        static volatile bool EverIsWriteLog = false;
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            var _request = context.HttpContext.Request;
            var _reqPath = _request.Path;
            var isNotApi = !((context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor).MethodInfo.ReflectedType.IsDefined(typeof(ApiControllerAttribute), true));
            //忽略预加载的请求日志
            var ignore = !_reqPath.HasValue || _reqPath.Value.ToLower().Contains("initdo") || _reqPath.Value.ToLower().Contains("initmvcdo");
            if (!WebLogHelper.Config.WriteOpen || isNotApi || ignore)
            {
                await next();
                return;
            }
            WebReqLog log = await CreateLog(context, _request);
            var _url = _reqPath.Value + (_request.QueryString.HasValue ? _request.QueryString.Value : "");// log.Query;
            ResourceExecutedContext resultContext = null;
            try
            {
                WebLogHelper.AddOrUpdateLog(log);
                //后续可能会通过配置文件的读写 来修改记录哪些urls
                //配置文件读写
                resultContext = await HandelNext(next, _url, log.Date,
                    //判断是否满足记录日志的条件
                    EverIsWriteLog || LogSqlPathConfigHelper.IsWriteLog(_reqPath.Value), (sqls) =>
                        log.Sqls = sqls
                    );//执行完时记录下来里面的sql
            }
            catch (Exception ex)
            {
                //注释 HandelNext里面的这行上面的锁 handList.OrderBy(h => h.date) 可以触发一个线程bug
                //利用这个机制也可以写代码来调试系统功能
                context.Result = new JsonResult(Comm.TypeFactory.CreateBizException("系统开了一点小差", errorData: ex));
            }
            if (resultContext == null) return;
            log.Complete = (DateTime.Now - log.Date).TotalSeconds;
            if (resultContext == null || resultContext.Result == null || (resultContext.Result is Microsoft.AspNetCore.Mvc.BadRequestObjectResult))//预计是实体验证不通过 需要调试测试
            {
                log.Success = false;
                log.Message = "实体验证不通过！";
                if ((resultContext.Result is Microsoft.AspNetCore.Mvc.BadRequestObjectResult))
                {
                    var bad = (resultContext.Result as Microsoft.AspNetCore.Mvc.BadRequestObjectResult);
                    log.Message += "详细错误信息见Data";
                    log.Data = JsonConvert.SerializeObject((resultContext.Result as Microsoft.AspNetCore.Mvc.BadRequestObjectResult).Value);
                }
            }
            var result = resultContext.Result;
            //发配到别的任务里处理序列化等
            Task.Run(() => UpdateLog(log, result));
        }


        private static async Task<WebReqLog> CreateLog(ResourceExecutingContext context, HttpRequest _request)
        {
            WebReqLog log = new WebReqLog();
            log.Date = DateTime.Now;
            log.Method = _request.Method;
            log.Path = (_request.Host.HasValue ? _request.Host.Value : "") + _request.Path.Value;
            log.Query = _request.QueryString.HasValue ? _request.QueryString.Value : "";
            Func<Task> loadBody = async () =>
            {
                var str = (context == null || context.HttpContext == null || _request == null) ? ""
                    : await ReadBodyAsync(_request);//如果等接口读完就读不出来了 这里先读取
                _request.EnableBuffering();//然后再允许读取 这样后面接口也能读取到
                log.Body = str;
            };
            try
            {
                if ((_request.ContentType ?? "").Contains("form-data") && _request.Form.Count > 0)
                {
                    var dic = new Dictionary<string, object>();
                    foreach (var item in _request.Form)
                        dic.Add(item.Key, string.Join(',', item.Value));
                    if (_request.Form.Files.Count > 0)
                        dic.Add("__提示", "存在文件，拦截器暂时无法记录文件！");
                    log.Body = JsonConvert.SerializeObject(dic);
                }
                else await loadBody();
            }
            catch (Exception)
            {
                await loadBody();
            }
            log.IsHttps = _request.IsHttps;
            log.ContentType = _request.ContentType;
            //本来这步是想放到线程里面  但有时会导致Headers 没有
            try
            {
                if (_request.Headers.Count != 0)
                {
                    await Task.Run(() =>
                    {
                        var headers = new Dictionary<string, object>();
                        foreach (var item in _request.Headers)
                            headers.Add(item.Key, string.Join(',', item.Value));
                        log.Headers = JsonConvert.SerializeObject(headers);
                    });
                }
            }
            catch (Exception ex)
            {
            }
            return log;
        }


        private static void UpdateLog(WebReqLog log, object Result)
        {
            Action func = () =>
            {
                if (Result is JsonResult)
                {
                    var result = Result as JsonResult;
                    if (result != null)
                    {
                        dynamic val = result.Value;
                        if (HandBizResultType(val, log)) ;
                        else
                        {
                            try
                            {
                                var s = GetVal(val, "Success") ?? GetVal(val, "success");
                                if (s is bool)
                                {
                                    log.Success = (bool)s;
                                    if (log.Success)
                                    {
                                        log.Data = GetVal(val, "Data") ?? GetVal(val, "data") ?? "";
                                    }
                                    log.Message = (GetVal(val, "Message") ?? GetVal(val, "Message") ?? "").ToString();
                                }
                                else
                                    log.Success = true;
                                log.Data = val;//取出Data
                            }
                            catch (Exception ex)
                            {
                            }
                        }
                    }
                }
                else if (Result is ObjectResult)
                {
                    var result = Result as ObjectResult;
                    if (result != null)
                    {
                        dynamic val = result.Value;
                        if (HandBizResultType(val, log)) ;
                        else
                        {
                            try
                            {
                                var s = GetVal(val, "Success") ?? GetVal(val, "success");
                                if (s is bool)
                                {
                                    log.Success = (bool)s;
                                    if (log.Success)
                                    {
                                        log.Data = GetVal(val, "Data") ?? GetVal(val, "data") ?? "";
                                    }
                                    log.Message = (GetVal(val, "Message") ?? GetVal(val, "Message") ?? "").ToString();
                                }
                                else
                                    log.Success = true;
                                log.Data = val;//取出Data
                            }
                            catch (Exception ex)
                            {
                            }
                        }
                    }
                }
                log.Data = JsonConvert.SerializeObject(log.Data);
            };
            lock (log)//锁定这个对象 因为在编辑他
            {
                func();
                log.Completed = true;
                WebLogHelper.AddOrUpdateLog(log, true);
            }
        }

        static Type BaseBizResultType;
        static object GetVal(object obj, string pn)
        {
            var ps = obj.GetType().GetProperties().ToList().Where(el => el.Name == pn);
            var _p = ps.FirstOrDefault(el => el.DeclaringType.Name.Contains("BizResult")) ??
                        ps.FirstOrDefault(el => el.DeclaringType.Name.Contains("BaseBizResult")) ??
                        ps.FirstOrDefault();
            if (_p != null)
                return _p.GetValue(obj, null);
            else return null;
        }

        /// <summary>
        /// 如果值是 那个类型 或子类 就处理
        /// </summary>
        /// <param name="val"></param>
        /// <param name="log"></param>
        /// <returns></returns>
        static bool HandBizResultType(dynamic val, WebReqLog log)
        {
            if (BaseBizResultType == null) BaseBizResultType = Comm.TypeFactory.GetBaseBizResultType();
            if (val.GetType() == BaseBizResultType || val.GetType().IsSubclassOf(BaseBizResultType))//是那个类型 或子类
            {
                log.Success = val.Success;
                log.Message = val.Message;
                log.Data = val.Data ?? GetVal(val, "Data");//取出Data
                return true;
            }
            else return false;
        }
        #region 从请求里获取body等

        private static async Task<string> ReadBodyAsync(HttpRequest request)
        {
            if (request.Body.CanRead)
            {
                request.EnableBuffering();
                //await EnableRewindAsync(request).ConfigureAwait(false);
                var encoding = GetRequestEncoding(request);
                return await ReadStreamAsync(request.Body, encoding).ConfigureAwait(false);
            }
            return null;
        }

        private static Encoding GetRequestEncoding(HttpRequest request)
        {
            var requestContentType = request.ContentType;
            var requestMediaType = requestContentType == null ? default(MediaType) : new MediaType(requestContentType);
            var requestEncoding = requestMediaType.Encoding;
            if (requestEncoding == null)
            {
                requestEncoding = Encoding.UTF8;
            }
            return requestEncoding;
        }

        private static async Task EnableRewindAsync(HttpRequest request)
        {
            if (!request.Body.CanSeek)
            {
                request.EnableBuffering();

                await request.Body.DrainAsync(CancellationToken.None);
                request.Body.Seek(0L, SeekOrigin.Begin);
            }
        }

        private static async Task<string> ReadStreamAsync(Stream stream, Encoding encoding)
        {
            using (StreamReader sr = new StreamReader(stream, encoding, true, 1024, true))//这里注意Body部分不能随StreamReader一起释放
            {
                var str = await sr.ReadToEndAsync();
                stream.Seek(0, SeekOrigin.Begin);//内容读取完成后需要将当前位置初始化，否则后面的InputFormatter会无法读取
                return str;
            }
        }

        #endregion 从请求里获取body等
    }
}